package cn.jbolt.core.db.dbpro;

import cn.jbolt.core.model.base.JBoltBaseModel;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.*;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.dialect.OracleDialect;
import com.jfinal.plugin.activerecord.dialect.PostgreSqlDialect;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.Map.Entry;

public class JBoltDbPro extends DbPro {

	public JBoltDbPro(String configName) {
		super(configName);
	}

	/**
	 * Batch update models using the attrs names of the first model in modelList.
	 * Ensure all the models can use the same sql as the first model.
	 */
	@Override
	public int[] batchUpdate(List<? extends Model> modelList, int batchSize) {
		if (modelList == null || modelList.size() == 0)
			return new int[0];

		Model model = modelList.get(0);
		boolean needAfterUpdate = false;
		Map<String, Boolean> updateKeyCacheMap = null;
		if (model instanceof JBoltBaseModel) {
			JBoltBaseModel bm = (JBoltBaseModel) model;
			// 检测是否需要执行beforeUpdate
			updateKeyCacheMap = new HashMap<String, Boolean>();
			Date date = new Date();
			JBoltBaseModel jbm;
			boolean updateDbKeyCache;
			for (Model m : modelList) {
				jbm = ((JBoltBaseModel) m);
				updateDbKeyCache = jbm.beforeUpdate(date);
				updateKeyCacheMap.put(jbm._getBatchUpdateKey(), updateDbKeyCache);
			}
			needAfterUpdate = true;
		}
		Table table = TableMapping.me().getTable(model.getClass());
		String[] pKeys = table.getPrimaryKey();
		Map<String, Object> attrs = CPI.getAttrs(model);
		List<String> attrNames = new ArrayList<String>();
		// the same as the iterator in Dialect.forModelSave() to ensure the order of the
		// attrs
		for (Entry<String, Object> e : attrs.entrySet()) {
			String attr = e.getKey();
			if (config.getDialect().isPrimaryKey(attr, pKeys) == false && table.hasColumnLabel(attr))
				attrNames.add(attr);
		}
		for (String pKey : pKeys)
			attrNames.add(pKey);
		String columns = StrKit.join(attrNames.toArray(new String[attrNames.size()]), ",");

		// update all attrs of the model not use the midifyFlag of every single model
		Set<String> modifyFlag = attrs.keySet(); // model.getModifyFlag();

		StringBuilder sql = new StringBuilder();
		List<Object> parasNoUse = new ArrayList<Object>();
		config.getDialect().forModelUpdate(TableMapping.me().getTable(model.getClass()), attrs, modifyFlag, sql,
				parasNoUse);
		int[] result = batch(sql.toString(), columns, modelList, batchSize);
		if (needAfterUpdate) {
			JBoltBaseModel jbm;
			for (Model m : modelList) {
				jbm = ((JBoltBaseModel) m);
				jbm.afterUpdate(updateKeyCacheMap.get(jbm._getBatchUpdateKey()));
			}
		}
		return result;
	}

	@Override
	public int[] batchSave(List<? extends Model> modelList, int batchSize) {
		if (modelList == null || modelList.size() == 0)
			return new int[0];

		Model model = modelList.get(0);
		boolean needAfterSave = false;
		if (model instanceof JBoltBaseModel) {
			JBoltBaseModel bm = (JBoltBaseModel) model;
			if (bm.checkNeedBeforeSaveInBatchSave()) {
				Date date = new Date();
				modelList.forEach(m -> {
					((JBoltBaseModel) m).beforeSaveInBatchSave(date);
				});
			}
			needAfterSave = true;
		}
		Map<String, Object> attrs = CPI.getAttrs(model);
		int index = 0;
		StringBuilder columns = new StringBuilder();
		// the same as the iterator in Dialect.forModelSave() to ensure the order of the
		// attrs
		for (Entry<String, Object> e : attrs.entrySet()) {
			if (config.getDialect() instanceof OracleDialect) { // 支持 oracle 自增主键
				Object value = e.getValue();
				if (value instanceof String && ((String) value).endsWith(".nextval")) {
					continue;
				}
			} else if (config.getDialect() instanceof PostgreSqlDialect) {
				Object value = e.getValue();
				if (value instanceof String && ((String) value).indexOf("nextval('") != -1) {
					continue;
				}
			}

			if (index++ > 0) {
				columns.append(',');
			}
			columns.append(e.getKey());
		}

		StringBuilder sql = new StringBuilder();
		List<Object> parasNoUse = new ArrayList<Object>();
		config.getDialect().forModelSave(TableMapping.me().getTable(model.getClass()), attrs, sql, parasNoUse);
		int[] result = batch(sql.toString(), columns.toString(), modelList, batchSize);
		if (needAfterSave) {
			modelList.forEach(m -> {
				((JBoltBaseModel) m).afterSave();
			});
		}
		return result;
	}

	/**
	 * Batch save records using the "insert into ..." sql generated by the first
	 * record in recordList. Ensure all the record can use the same sql as the first
	 * record.
	 *
	 * @param tableName the table name
	 */
	@Override
	public int[] batchSave(String tableName, List<? extends Record> recordList, int batchSize) {
		if (recordList == null || recordList.size() == 0)
			return new int[0];

		Record record = recordList.get(0);
		Map<String, Object> cols = record.getColumns();
		int index = 0;
		StringBuilder columns = new StringBuilder();
		// the same as the iterator in Dialect.forDbSave() to ensure the order of the
		// columns
		for (Entry<String, Object> e : cols.entrySet()) {
			if (config.getDialect() instanceof OracleDialect) { // 支持 oracle 自增主键
				Object value = e.getValue();
				if (value instanceof String && ((String) value).endsWith(".nextval")) {
					continue;
				}
			} else if (config.getDialect() instanceof PostgreSqlDialect) {
				Object value = e.getValue();
				if (value instanceof String && ((String) value).indexOf("nextval('") != -1) {
					continue;
				}
			}

			if (index++ > 0) {
				columns.append(',');
			}
			columns.append(e.getKey());
		}

		String[] pKeysNoUse = new String[0];
		StringBuilder sql = new StringBuilder();
		List<Object> parasNoUse = new ArrayList<Object>();
		config.getDialect().forDbSave(tableName, pKeysNoUse, record, sql, parasNoUse);
		return batch(sql.toString(), columns.toString(), recordList, batchSize);
	}

	@Override
	public boolean save(String tableName, String primaryKey, Record record) {
		// TODO Auto-generated method stub
		return super.save(tableName, primaryKey, record);
	}

	@Override
	protected boolean save(Config config, Connection conn, String tableName, String primaryKey, Record record)
			throws SQLException {
		String[] pKeys = primaryKey.split(",");
		List<Object> paras = new ArrayList<Object>();
		StringBuilder sql = new StringBuilder();
		config.getDialect().forDbSave(tableName, pKeys, record, sql, paras);

		try (PreparedStatement pst = (config.getDialect() instanceof OracleDialect
				|| config.getDialect() instanceof PostgreSqlDialect) ? conn.prepareStatement(sql.toString(), pKeys)
						: conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS)) {
			config.getDialect().fillStatement(pst, paras);
			int result = pst.executeUpdate();
			config.getDialect().getRecordGeneratedKey(pst, record, pKeys);
			return result >= 1;
		}
	}

}
